/**
 * Copyright (c) 2016-2021, Bosco.Liao (bosco_liao@126.com).
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package org.iherus.shiro.redis.spring.boot.autoconfigure;

import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.iherus.shiro.cache.redis.config.DefaultPoolConfig;
import org.iherus.shiro.cache.redis.config.RedisClusterConfiguration;
import org.iherus.shiro.cache.redis.config.RedisSentinelConfiguration;
import org.iherus.shiro.cache.redis.config.RedisStandaloneConfiguration;
import org.iherus.shiro.cache.redis.connection.RedisConnectionFactory;
import org.iherus.shiro.cache.redis.connection.jedis.JedisConnectionFactory;
import org.iherus.shiro.redis.spring.boot.autoconfigure.ShiroRedisProperties.JedisConfig;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPoolConfig;

/**
 * 
 * @author Bosco.Liao
 * @since 2.0.0
 */
@Configuration
@ConditionalOnClass({ GenericObjectPool.class, Jedis.class })
@ConditionalOnMissingClass("org.springframework.data.redis.connection.RedisConnectionFactory")
public class JedisConnectionFactoryConfiguration extends RedisConfigurationWrapper {

	private final ShiroRedisProperties properties;

	private final ObjectProvider<JedisSslClientConfigurationCustomizer> customizerProvider;

	protected JedisConnectionFactoryConfiguration(ShiroRedisProperties properties,
                                                  ObjectProvider<RedisStandaloneConfiguration> standaloneConfigProvider,
                                                  ObjectProvider<RedisSentinelConfiguration> sentinelConfigProvider,
                                                  ObjectProvider<RedisClusterConfiguration> clusterConfigProvider,
                                                  ObjectProvider<JedisSslClientConfigurationCustomizer> customizerProvider) {

		super(properties, standaloneConfigProvider, sentinelConfigProvider, clusterConfigProvider);
		this.properties = properties;
		this.customizerProvider = customizerProvider;
	}

	@Bean(name = "x_jedisConnectionFactory", initMethod = "init", destroyMethod = "destroy")
	@ConditionalOnMissingBean(RedisConnectionFactory.class)
	public JedisConnectionFactory connectionFactory() {
		return createJedisConnectionFactory();
	}

	@SuppressWarnings("rawtypes")
	private JedisConnectionFactory createJedisConnectionFactory() {
		GenericObjectPoolConfig poolConfig = createJedisPoolConfig(this.properties.getJedis().getPool());

		RedisSentinelConfiguration sentinelConfig = null;
		RedisClusterConfiguration clusterConfig = null;

		JedisConnectionFactory factory = null;
		
		if ((sentinelConfig = getSentinelConfiguration()) != null) {
			factory = new JedisConnectionFactory(sentinelConfig, poolConfig);
		} else if ((clusterConfig = getClusterConfiguration()) != null) {
			factory = new JedisConnectionFactory(clusterConfig, poolConfig);
		} else {
			factory = new JedisConnectionFactory(getStandaloneConfiguration(), poolConfig);
		}
		
		applyProperties(factory);
		return factory;
	}

	private void applyProperties(JedisConnectionFactory factory) {
		JedisConfig jedisConfig = this.properties.getJedis();
		JedisSslClientConfiguration sslConfig = getJedisSslClientConfiguration();

		factory.setClientName(jedisConfig.getClientName());
		factory.setConnectTimeout(jedisConfig.getConnectTimeout());
		factory.setSoTimeout(jedisConfig.getSoTimeout());

		// SSL
		factory.setUseSsl(jedisConfig.isSsl());
		factory.setSslSocketFactory(sslConfig.getSslSocketFactory());
		factory.setSslParameters(sslConfig.getSslParameters());
		factory.setHostnameVerifier(sslConfig.getHostnameVerifier());
	}

	private JedisSslClientConfiguration getJedisSslClientConfiguration() {
		JedisSslClientConfiguration.Builder builder = JedisSslClientConfiguration.builder();
		customize(builder);
		return builder.build();
	}

	@SuppressWarnings("rawtypes")
	private GenericObjectPoolConfig createJedisPoolConfig(ShiroRedisProperties.Pool settingPoolConfig) {
		if (settingPoolConfig == null) {
			return new DefaultPoolConfig();
		}
		JedisPoolConfig config = new JedisPoolConfig();
		config.setMinIdle(settingPoolConfig.getMinIdle());
		config.setMaxIdle(settingPoolConfig.getMaxIdle());
		config.setMaxTotal(settingPoolConfig.getMaxTotal());
		config.setMaxWaitMillis(settingPoolConfig.getMaxWaitMillis());
		config.setTestOnBorrow(settingPoolConfig.isTestOnBorrow());
		config.setTestWhileIdle(settingPoolConfig.isTestWhileIdle());
		config.setMinEvictableIdleTimeMillis(settingPoolConfig.getMinEvictableIdleTimeMillis());
		config.setTimeBetweenEvictionRunsMillis(settingPoolConfig.getTimeBetweenEvictionRunsMillis());
		config.setNumTestsPerEvictionRun(settingPoolConfig.getNumTestsPerEvictionRun());

		return config;
	}

	private void customize(JedisSslClientConfiguration.Builder builder) {
		this.customizerProvider.orderedStream().forEach((customizer) -> customizer.customize(builder));
	}

}
